/*
 * edbd.c - EnCluster Base DB Daemon (process name: edbd)
 * Clunix, glory@clunix.com, yjlee@clunix.com, 2001.7.19.
 * Clunix, cezanne@clunix.com, 2005.2
 *
 * Copyright (c) 2001-2005 by Clunix Inc. All Rights Reserved.
 */

#include "config.h"

#include "edbd.h"

#include "license.h"

sr_t		*sr_edbd_conf;
sr_t		*sr_edbroot;
sr_t		*sr_edbd_xe_forms;

ebc_clid_t	self_clid;
ebc_ndid_t	self_ndid, self_msid;
ebc_mdid_t	self_mdid;
xid_t		self_node_xid;

char		*self_clname, *self_ndname, *self_msname;

static ECMBOOL	is_daemon;

ecm_uint16	edbd_ebc_port;
ecm_dbg_t	edbd_dbg;

static ECMBOOL	edbd_skip_license_checking;

#ifdef ECM_DEBUG

ECMBOOL edbd_quick_debug_mode;
ECMBOOL	edbd_suppress_logging;
ECMBOOL edbd_suppress_autoreactor;

#endif

static char	*logfile;

ECMBOOL		edbd_master_enabled = ECM_FALSE;

ecm_uint32	master_detection_time = 1000;	/* in msecs */
ecm_uint32	master_announce_time = 1000;	/* in msecs */
ecm_uint32	master_detection_retry = 5;
ecm_uint32	master_announce_retry = 5;

ecm_uint32	heartbeat_miss_time = 15;	/* in seconds */

/* EDBD - EBCD connection */
ecm_ebc_t	edbd_ec = NULL;

/* Run-time argument > Environment variable > Default */
const char	*edbd_snapshot;
const char	*edbd_initial_snapshot;
const char	*edbd_modname;
const char	*edbd_modver;
const char	*edbd_clxdir;
const char	*edbd_modules_dir;

/* Inter-EDBD communication without using ebc_call */
/* EBC fd callback is used */
/* When releasing extern EBC, it will be deprecated */

static void
signal_handler(ecm_signo_t signo)
{
	switch (signo) {
	case ECM_SIGINT:
	case ECM_SIGTERM:
		ECMVERBOSE("Received termination signal");
		edbd_shutdown_self();
		break;
	default:
		break;
	}
}

static void
usage(void)
{
	fprintf(stderr, "edbd 2.0\n");
	fprintf(stderr, "Usage: edbd [Options]\n");
	fprintf(stderr, "  -D : run as daemon\n");
	fprintf(stderr, "  -C : clear snapshot\n");
	fprintf(stderr, "  -m module_name\tedbd module name\n");
	fprintf(stderr, "  -p ebc_port\t\tebc port to connect\n");
	fprintf(stderr, "  -H <CLX_HOME>\n");
	fprintf(stderr, "  -s <snapshot file>\tsnapshot file\n");
	fprintf(stderr, "  -i <secs>\tsnapshot interval\n");
	fprintf(stderr, "  -l <log file>\n");
	fprintf(stderr, "  -v\t\t\tverbose mode\n");
	fprintf(stderr, "  -t\t\t\tcan't run as master\n");
	fprintf(stderr, "  -f\t\t\tforcibly run master hook command\n");
	fprintf(stderr, "  -V license_key\tshow license key info and quit immediately\n");
#ifdef ECM_DEBUG
	fprintf(stderr, "  -q: edbd quick mode\n");
	fprintf(stderr, "  -x: disable sending log\n");
	fprintf(stderr, "  -X: disable autoreactor\n");
	fprintf(stderr, "  -G: disable license checking\n");
	fprintf(stderr, "  -d <debug_set> : debug message\n");
	fprintf(stderr, "     g: edbd general\n");
	fprintf(stderr, "     s: snapshot\n");
	fprintf(stderr, "     e: election\n");
	fprintf(stderr, "     c: client request\n");
	fprintf(stderr, "     i: init\n");
	fprintf(stderr, "     n: connection\n");
	fprintf(stderr, "     u: update\n");
	fprintf(stderr, "     x: XE\n");
	fprintf(stderr, "     l: logging\n");
	fprintf(stderr, "     r: reactor\n");
	fprintf(stderr, "     R: auto reactor\n");
	fprintf(stderr, "     p: lnpsensor\n");
	fprintf(stderr, "     L: tree lock\n");
	fprintf(stderr, "     d: deletion\n");
	fprintf(stderr, "     M: module\n");
	fprintf(stderr, "     U: ECX UI related\n");
	fprintf(stderr, "  -c <debug_set> : ebc layer debug set\n");
#endif
}

static void
setup_clxdir(const char *clxdir)
{
	if (edbd_clxdir != NULL)
		return;
	if (ecm_empty_str(clxdir))
		return;
	edbd_clxdir = ecm_strdup(clxdir);
	ecm_setenv(ECM_CLX_HOME_ENV, edbd_clxdir);
	ecm_set_default_clxdir(edbd_clxdir);
}

static ECMBOOL
setup_ebcport(const char *ebcport)
{
	if (edbd_ebc_port == 0)
		return ECM_TRUE;
	if (ecm_empty_str(ebcport))
		return ECM_FALSE;
	if (ecm_sscanf(ecm_optarg, "%hu", &edbd_ebc_port) != 1)
		return ECM_FALSE;
	ecm_setenv(ECM_EBC_PORT_ENV, "%hu", edbd_ebc_port);
	return ECM_TRUE;
}

static void
setup_env(void)
{
	setup_clxdir(ecm_getenv(ECM_CLX_HOME_ENV));

	if (ecm_empty_str(edbd_modname))
		edbd_modname = ecm_getenv(EDBD_ENV_MODNAME);
	if (ecm_empty_str(edbd_modver))
		edbd_modver = ecm_getenv(EDBD_ENV_MODVER);
	setup_ebcport(ecm_getenv(ECM_EBC_PORT_ENV));
}

#ifdef ECM_DEBUG

ecm_hashtable_t	mod_dbghash;

ECMBOOL
setup_debugset(const char *str_dbgset)
{
	ecm_uint32	dbgset = 0;
	const char	*p = str_dbgset;

	while (*p) {
		switch (*p) {
		case 'a':
			dbgset = DBG_ALL;
			break;
		case 'g':
			dbgset |= DBG_EDBD;
			break;
		case 's':
			dbgset |= DBG_SNAPSHOT;
			break;
		case 'e':
			dbgset |= DBG_ELECT;
			break;
		case 'c':
			dbgset |= DBG_CLIREQ;
			break;
		case 'U':
			dbgset |= DBG_ECXUI;
			break;
		case 'i':
			dbgset |= DBG_INIT;
			break;
		case 'n':
			dbgset |= DBG_CONN;
			break;
		case 'u':
			dbgset |= DBG_UPDATE;
			break;
		case 'x':
			dbgset |= DBG_XE;
			break;
		case 'l':
			dbgset |= DBG_LOGGING;
			break;
		case 'r':
			dbgset |= DBG_REACTOR;
			break;
		case 'R':
			dbgset |= DBG_AREACTOR;
			break;
		case 'p':
			dbgset |= DBG_LNPSENSOR;
			break;
		case 'L':
			dbgset |= DBG_TREELOCK;
			break;
		case 'd':
			dbgset |= DBG_DEL;
			break;
		case 'M':
			dbgset |= DBG_MODDBG;
			break;
		default:
			return ECM_FALSE;
		}
		p++;
	}
	
	ecm_set_debugset(edbd_dbg, dbgset);
	return ECM_TRUE;
}

static ECMBOOL
do_debugset(const char *c_dbgset)
{
	char	*dbgset, *p;
	ECMBOOL	res;

	dbgset = ecm_strdup(c_dbgset);
	if ((p = ecm_strchr(dbgset, ':'))) {
		char	*modname, *data;

		*p = '\0';
		modname = dbgset;
		data = ecm_strdup(p + 1);
		ecm_hashtable_str_insert(mod_dbghash, data, modname);
		ecm_add_debugset(edbd_dbg, DBG_MODDBG);
		res = ECM_TRUE;
	}
	else
		res = setup_debugset(dbgset);
	ecm_free(dbgset);
	return res;
}

static ECMBOOL
parse_debugset(const char *c_dbgset)
{
	char	*dbgset = ecm_strdup(c_dbgset);
	char	*p, *comma;

	p = dbgset;
	while (ECM_TRUE) {
		if ((comma = ecm_strchr(p, ','))) {
			*comma = '\0';
		}
		if (!do_debugset(p))
			return ECM_FALSE;
		if (comma == NULL)
			break;
		p = comma + 1;
	}
	ecm_free(dbgset);
	return ECM_TRUE;

}

static ECMBOOL
setup_ebc_debugset(const char *str_dbgset)
{
	return ebc_set_debugset_str(str_dbgset);
}

#endif

static ECMBOOL
parse_args(int argc, char *argv[])
{
	int	c;

	/* Parse options */
	while ((c = ecm_getopt(argc, argv, "DCh:H:m:p:s:i:l:d:c:vqXxM:G")) != -1) {
		switch(c) {
		case 'D':
			is_daemon = ECM_TRUE;
			break;
		case 'C':
			is_clear_snapshot = ECM_TRUE;
			break;
		case 'm': 
			if (edbd_modname == NULL)
				edbd_modname = ecm_strdup(ecm_optarg);
			break;
		case 'p':
			if (!setup_ebcport(ecm_optarg))
				return ECM_FALSE;
			break;
		case 's':
			edbd_snapshot = ecm_strdup(ecm_optarg);
			break;
		case 'i':
			if (snapshot_interval == 0) {
				if (ecm_sscanf(ecm_optarg, "%u", &snapshot_interval) != 1 ||
				    snapshot_interval == 0) {
					fprintf(stderr, "%s: invalid snapshot interval", ecm_optarg);
					return ECM_FALSE;
				}
				snapshot_interval *= 1000;
			}
			break;
		case 'l':
			if (logfile == NULL)
				logfile = ecm_strdup(ecm_optarg);
			break;
		case 'H':
			setup_clxdir(ecm_optarg);
			break;
		case 'v':
			ecm_set_verbose_mode(ECM_TRUE);
			break;
		case 'h':
			usage();
			exit(0);
#ifdef ECM_DEBUG
		case 'M':
			if (!ebc_dbg_setup_idmap(ecm_optarg))
				fprintf(stderr, "%s: cannot open\n", ecm_optarg);
			break;
		case 'd':
			if (!parse_debugset(ecm_optarg))
				return ECM_FALSE;
			break;
		case 'c':
			if (!setup_ebc_debugset(ecm_optarg))
				return ECM_FALSE;
			break;
		case 'q':
			edbd_quick_debug_mode = ECM_TRUE;
			master_detection_time = 800;
			master_announce_time = 800;
			heartbeat_miss_time = 7;
			break;
		case 'X':
			edbd_suppress_autoreactor = ECM_TRUE;
			break;
		case 'x':
			edbd_suppress_logging = ECM_TRUE;
			break;
		case 'G':
			edbd_skip_license_checking = ECM_TRUE;
			break;
#endif
		default:
			return ECM_FALSE;
		}
	}

	return ECM_TRUE;
}

static void
setup_default(void)
{
	char	*p;

	if (edbd_modname == NULL)
		edbd_modname = EDBD_DEFAULT_MODNAME;

	/* Set the location of snapshot file */
	if (edbd_snapshot == NULL)
		edbd_snapshot = ecm_gen_path(ecm_default_clxdir(), "rc", EDBD_DEFAULT_SNAPSHOT, NULL);
	if (edbd_initial_snapshot == NULL)
		edbd_initial_snapshot = ecm_gen_path(ecm_default_clxdir(), "rc", EDBD_INITIAL_SNAPSHOT, NULL);

	if (edbd_modules_dir == NULL)
		edbd_modules_dir = ecm_gen_path(NULL, "modules", NULL);

	ecm_asprintf(&p, "%d.%d.%d",
		     (EDBD_VERSION >> 16),
		     (EDBD_VERSION & 0x00ff00) >> 8,
		     (EDBD_VERSION & 0x0000ff));
	edbd_modver = p;
}

static ECMBOOL
setup_ebc_connection(void)
{
	ebc_err_t	err;
	ecm_uint32	count = 0;

	EDBDDBG(DBG_EDBD, "connecting to ebcd with port = %d", edbd_ebc_port);

again:
	if ((err = ebc_connect(NULL, edbd_ebc_port, edbd_modname, 5000, &edbd_ec)) != EBC_OK) {
		switch (err) {
		case -ERR_EBC_EXIST:
			ECMERR("another edbd already exist.");
			break;
		case -ERR_EBC_CONNECT:
			EDBDDBG(DBG_EDBD, "connect failed. retry after 1 sec..");
			ecm_sleep(1000);
			if (!edbd_going_to_shutdown && count++ < 7)
				goto again;
			EDBDDBG(DBG_EDBD, "too many connection failures. retry aborted..");

		default:
			ECMERR("EBC connection failed: %d", err);
			break;
		}
		return ECM_FALSE;
	}

	ECMVERBOSE("EBC connection established");

	if (ebc_ecb_add(edbd_ec, "clireq", edbd_cb_clireq, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "detect", edbd_cb_detect, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "announce", edbd_cb_announce, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "getdb", edbd_cb_getdb, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "getlog", edbd_cb_getlog, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "updatesync", edbd_cb_updatesync, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "update", edbd_cb_update, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "fileop", edbd_cb_fileop, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "exec", edbd_cb_exec, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "rexec", edbd_cb_rexec, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "handover", edbd_cb_handover, NULL) != EBC_OK ||
	    ebc_ecb_add(edbd_ec, "inform", edbd_cb_inform, NULL) != EBC_OK) {
		ECMERR("ebc setup error");
		ebc_close(edbd_ec);
		edbd_ec = NULL;
		return ECM_FALSE;
	}
	return ECM_TRUE;
}

static void
parse_args_from_cmdline(const char *c_cmdline)
{
	char	*cmdline;
	int	argc;
	char	**argvs;

	if (ecm_empty_str(c_cmdline))
		return;
	cmdline = ecm_strdup(c_cmdline);
	argc = ecm_get_argvs_from_cmdline(cmdline, &argvs);
	if (argc > 0) {
		ecm_getopt_clear();
		parse_args(argc, argvs);
	}
	ecm_free(argvs);
	ecm_free(cmdline);
}

static void
load_conf(void)
{
	sr_t	*sr_conf, *sr_edbd_conf;
	char	*confpath;

	confpath = ecm_gen_path(edbd_clxdir, "etc", "econf", NULL);
	if (!eds_read_sr_conf(confpath, &sr_conf)) {
		ecm_free(confpath);
		return;
	}
	ecm_free(confpath);

	sr_edbd_conf = sr_getc(sr_conf, "edbd");

	parse_args_from_cmdline(sr_getc_str(sr_edbd_conf, "arg"));

	if (edbd_ebc_port == 0)
		edbd_ebc_port = sr_getc_int(sr_conf, "ebc_port");

	if (edbd_ebc_port != 0)
		ecm_setenv(ECM_EBC_PORT_ENV, "%hu", edbd_ebc_port);

	edbd_master_enabled = sr_getc_bool(sr_edbd_conf, "master_enabled");
	ECMVERBOSE("LOAD ECONF: master_enabled=%d", edbd_master_enabled);

	sr_free(sr_conf);
}

#ifdef _WIN32
static void
edbd_stop_service(ecm_ptr_t ctx)
{
	edbd_shutdown_self();
}
#endif

static void
setup_logfile(void)
{
	FILE	*logfp = NULL;

	if (ecm_strcmp(logfile, ".") == 0) {
		char	*logpath;

		logpath = ecm_gen_rpath(NULL, "log/edbd.log");
		logfp = ecm_fopen(logpath, "a+");
		ecm_free(logpath);
	}
	else if (!ecm_empty_str(logfile))
		logfp = ecm_fopen(logfile, "a+");
	if (logfp == NULL)
		logfp = stderr;
	ecm_set_output_file(logfp);
}

int
main(int argc, char *argv[], char *envs[])
{
	ecm_daemon_check("edbd", "ECM edbd", "ebcd", main, argc, argv);

#ifdef _WIN32
	ecm_daemon_reg_stopper(edbd_stop_service, NULL);
#endif
	ebc_initialize();
	ecm_set_process_priority(1);

#ifdef ECM_DEBUG
	edbd_dbg = ecm_create_dbg();
	mod_dbghash = ecm_hashtable_create(16);
#endif

	ecm_signal(ECM_SIGINT, signal_handler);
	ecm_signal(ECM_SIGTERM, signal_handler);

	if (!parse_args(argc, argv)) {
		usage();
		return 1;
	}

	setup_env();

	load_conf();

	setup_logfile();

	ECMVERBOSE("\nStarting edbd...");

	setup_default();

	if (edbd_master_enabled) {
		if (!edbd_skip_license_checking && !license_check()) {
			ECMERR("Invalid or expired license key");
			ECMVERBOSE("Shutting down edbd...");
			return 2;
		}
	}
	else {
		ECMVERBOSE("Skip license key check (Slave-Only Node)");
	}

	if (!eds_initialize()) {
		ECMERR("type initialization failed!");
		return 3;
	}

	if (!setup_ebc_connection())
		return 3;

	if (!edbd_init())
		return 3;

	if (is_daemon) {
		ecm_daemonize(edbd_clxdir, "edbd");
		ecm_daemon_set_running();
	}

	edbd_start_updater();

	if (ebc_app_loop(edbd_ec, 0) != EBC_OK)
		ECMERR("ebc_app_loop() error");

	edbd_finalize();

	ECMVERBOSE("disconnect from ebcd");

	if (edbd_ec)
		ebc_close(edbd_ec);

	edbd_finalize_snapshot();

	ECMVERBOSE("edbd terminated");

	ecm_daemon_cleanup();

	return 0;
}

#ifdef ECM_DEBUG

/* This is just a dummy for sr_fancy_print() linkage */
void
dummy_sr_fancy_print(void)
{
	sr_fancy_print(NULL, NULL, 2, "");
}

#endif
